<script setup lang="ts">
import { CSSProperties, computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import { Table as Tb, Selection, Position, SingleDirection, Direction, TableData, tableDefaultValue, cellTextColorDefault, cellBgColorDefault } from './models/Table';
import { AurStateStore } from '@aurouscia/au-undo-redo'
import { ShortcutListener } from '@aurouscia/keyboard-shortcut';
import FloatTools from './components/FloatTools.vue'
import undoImg from './assets/undo.svg';
import redoImg from './assets/redo.svg';
import settingsImg from './assets/settings.svg';
import mutiSelectImg from './assets/mutiSelect.svg';
import saveImg from './assets/save.svg';
import editStructureImg from './assets/editStructure.svg';
import editContentImg from './assets/editContent.svg';
import colorApplyImg from './assets/colorApply.svg';
import colorReadImg from './assets/colorRead.svg';
import colorViewImg from './assets/colorView.svg';
import stickyOptionImg from './assets/stickyOption.svg';
import { SizeChange } from './models/etc/sizeChange';
import { SaveFunc } from './models/etc/saveFunc';
import { SaveResCallback } from './models/etc/saveResCallback';
import FloatToolsForLine from './components/FloatToolsForLine.vue';
import { FloatToolsEmitType } from './models/floatTools/floatToolsEmit';
import { FloatToolsMergeBtnStatus } from './models/floatTools/floatToolsMergeBtnStatus';

const loadDataTimeoutMs = 5000;
const saveTimeoutMs = 5000;

//真对不起，我也不想搞出回调地狱来，要怪就怪ts类型推断的缺陷
const props = defineProps<{
    tableData?:TableData,
    externalSaveCallbackProvide?:(save:SaveFunc)=>void,
    sizeChangeCallback?:(data:SizeChange)=>void
    noAds?:boolean,
    noShortcut?:boolean
}>()
const emit = defineEmits<{
    (e: 'save', value: TableData, callBack:SaveResCallback): void
    (e: 'changed'): void
}>()
defineExpose({
    loadData,
    save
})

function loadData(t:TableData){
    if(!t){
        return;
    }
    if(loadComplete.value){
        console.error("加载已完成，拒绝写入更新")
        return;
    }
    data.value = new Tb(t);
    selection.value = [];
    loadComplete.value = true;
    showInfo.value = false;
    saveToHistory(true);
    sizeChanged();
}

const loadComplete = ref<boolean>(false);
var initData:TableData = props.tableData || tableDefaultValue
const t = new Tb(initData);
const data = ref<Tb>(t);
const urAvai = ref<{u:boolean,r:boolean}>({u:false,r:false});
const ss = new AurStateStore<TableData>(20,(u,r)=>{urAvai.value={u,r}});
function saveToHistory(noEmitChanged?:boolean){
    ss.push(data.value.getData())
    if(!noEmitChanged)
        changed();
}
const loadHistory = (d:TableData)=>{data.value.replaceData(d);selection.value=[]}
if(props.tableData){
    loadComplete.value = true;
    saveToHistory(true);
    sizeChanged();
}else{
    window.setTimeout(()=>{
        if(!loadComplete.value){
            info.value = "数据加载失败，请检查网络连接";
            isErrInfo.value = true;
            showInfoBox(false);
        }
    },loadDataTimeoutMs)
}

if(props.externalSaveCallbackProvide){
    props.externalSaveCallbackProvide(save);
}

const controlDiv = ref<HTMLDivElement>();
function topbarHeight(){
    return controlDiv.value?.clientHeight || 40
}
function changed(){
    emit('changed');
    sizeChanged();
}
function sizeChanged(){
    if(props.sizeChangeCallback){
        nextTick(()=>{
            let totalHeight = 55;
            totalHeight += topbarHeight();
            if(!tableBody.value)
                return;
            const allTrs = tableBody.value.getElementsByTagName("tr");
            if(allTrs){
                for(const tr of allTrs){
                    totalHeight += tr.clientHeight + 2;
                }
            }
            if(props.sizeChangeCallback)
                props.sizeChangeCallback({height:totalHeight});
        })
    }
}

const info = ref<string>("");
const showInfo = ref<boolean>(false);
const isErrInfo = ref<boolean>(false);
var timer1=0,timer2=0;
function undo(){
    const res = ss.undo();
    if(res){
        loadHistory(res);
        changed()
    }
    hideFloatTools();
}
function redo(){
    const res = ss.redo();
    if(res){
        loadHistory(res);
        changed()
    }
    hideFloatTools();
}
function save():Promise<boolean>{
    return new Promise<boolean>((resolve, _) => {
        if(!loadComplete.value){
            info.value = "还未完成加载，不能保存";
            isErrInfo.value = true;
            showInfoBox(false);
            resolve(false);
            return;
        }
        isErrInfo.value = false;
        info.value = "... ..."
        showInfoBox(false);
        const expireTimer = window.setTimeout(() => {
            isErrInfo.value = true;
            info.value = "保存异常(超时)"
            showInfoBox();
            resolve(false);
        }, saveTimeoutMs)
        emit('save', data.value.getData(), (success, msg) => {
            window.clearTimeout(expireTimer);
            if (!success) {
                isErrInfo.value = true;
                info.value = msg || "保存失败";
                resolve(false);
            } else {
                isErrInfo.value = false;
                info.value = msg || "保存成功";
                resolve(true);
            }
            showInfoBox();
        })
    });
}
function showInfoBox(autoHide = true) {
    showInfo.value = true;
    window.clearTimeout(timer1);
    window.clearTimeout(timer2);
    if(autoHide){
        timer1 = window.setTimeout(() => {
            showInfo.value = false
        }, 1500)
        timer2 = window.setTimeout(() => {
            info.value = ''
            isErrInfo.value = false;
        }, 2000)
    }
}
const infoBoxTopStyle = computed<CSSProperties>(()=>{
    if(showInfo.value){
        return {top:topbarHeight()+'px'}
    }else{
        return {}
    }
})

const selection = ref<Selection>([]);
var dragStart:Position|undefined=undefined;
var dragNow:Position|undefined=undefined;
var newlyAddedDom:HTMLElement|undefined=undefined;
var mouseDownTime = 0;
const isDragging = ref<boolean>();
function mouseCell(e: MouseEvent, p: Position, type: "drag" | "click" | "down" | "up") {
    var changed = false;
    ateLineOp.value = undefined;
    if (type=="down" && !selectMode.value){
        mouseDown.value = true;
        mouseDownTime = Date.now();
        selection.value = [];
        dragStart = p; dragNow = p; isDragging.value = true;
        hideMergeBtn.value = false;
        hideFloatTools();
        return;
    }
    else if (type=="up" && !selectMode.value){
        dragStart = undefined;dragNow = undefined;
        mouseDown.value = false;
        isDragging.value = false;
        showFloatToolsEntrance(e)
        newlyAddedDom = undefined;
        return;
    }
    else if (type == "drag" && !selectMode.value) {
        if (mouseDown.value) {
            if(dragNow!=p){
                changed = true;
                if(e.target && (e.target as HTMLElement)?.tagName?.toLowerCase()=='td'){
                    newlyAddedDom = e.target as HTMLElement;
                }
            }
            dragNow = p;
        }
    }
    else if (type == "click") {
        const idx = selectionIdx(p);
        if (idx == -1) {
            selection.value?.push(p);
            hideMergeBtn.value = false;
            changed = true;
            refreshMergeBtnsHidding();
            hideFloatTools(false);
            showFloatToolsEntrance(e);
        } else {
            const m = data.value.isMergeInfoHead(p.row,p.col)
            if(m){
                const rem:Position[] = [];
                data.value.enumerateMergeInfo(m,(row,col)=>{rem.push({row,col});return true;})
                selection.value = selection.value.filter(p=>!rem.find(x=>x.row==p.row&&x.col==p.col));
            }
            else{
                selection.value.splice(idx, 1)
            }
            refreshMergeBtnsHidding();
            hideFloatTools(false);
            showFloatToolsEntrance(e);
        }
    }
    if(changed){
        if(type!="click" && dragStart && dragNow){
            selection.value = data.value.rectanglifySelection([dragStart,dragNow],[])
        }
        if(type=="click" && selectMode.value){
            selection.value = data.value.rectanglifySelection(selection.value,[]);
            refreshMergeBtnsHidding();
        }
        hideDelMergeBtn.value = !data.value.isInsideSingleMergeSelection(selection.value);
    }
}
function mouseCell_end(){
    //当鼠标在单元格缝里时松开，选取过程不会结束，所以让tbody的mouseup做这件事
    dragStart = undefined;dragNow = undefined;isDragging.value = false;
    if(newlyAddedDom){
        showFloatToolsEntrance(newlyAddedDom);
    }
    mouseDown.value = false;
}
function selectionIdx(p:Position){
    return selection.value?.findIndex(x=>x.col==p.col && x.row==p.row)
}
function mergeSelections(){
    if(selection.value.length<=1){
        return;
    }
    selection.value = data.value.addMerge(selection.value);
    saveToHistory();
    refreshMergeBtnsHidding();
    selectMode.value = false;
}
function removeItsMerge(){
    data.value.removeMerge(selection.value[0]);
    saveToHistory();
    refreshMergeBtnsHidding();
}

const editMode = ref<boolean>(true);
function textareaBlurHandler(e:FocusEvent, ridx:number, cidx:number){
    if(!e){
        return;
    }
    const cellPos = data.value.shownPosMapToCellPos({row:ridx, col:cidx})
    const originalContent = data.value.cells[cellPos.row][cellPos.col];
    const content = (e.target as HTMLTextAreaElement).value;
    if(content === originalContent){
        return;
    }
    data.value.cells[cellPos.row][cellPos.col] = content;
    data.value.refreshCellShown();
    saveToHistory();
}

function insertLine(dir:SingleDirection){
    selection.value = data.value.insert(selection.value, dir);
    hideFloatTools();
    saveToHistory();
}
function deleteLine(dir:Direction){
    selection.value = data.value.delete(selection.value, dir);
    saveToHistory();
}
function insertLineOnEdge(dir:"right"|"bottom"){
    selection.value = data.value.insert(undefined, dir);
    hideFloatTools();
    saveToHistory();
}
function ateFloatToolsEmiteHandler(e:FloatToolsEmitType){
    if(selection.value.length==0){return;}
    if(e=='insert_left'){
        insertLine('left');
    }else if(e=='insert_right'){
        insertLine('right');
    }else if(e=='insert_bottom'){
        insertLine('bottom');
    }else if(e=='insert_top'){
        insertLine('top');
    }else if(e=='delete_horizontal'){
        deleteLine('horizontal');
    }else if(e=='delete_vertical'){
        deleteLine('vertical');
    }else if(e=='merge'){
        mergeSelections();
    }else if(e=='mergeDel'){
        removeItsMerge();
    }
    selectMode.value = false;
    hideFloatTools();
    hideFloatToolsEntrance();
    ateLineOp.value = undefined;
}

var timer=0;
const scrollThrs = 70;
var runningHoriDir:"left"|"right"|undefined;
var runningVertDir:"up"|"down"|undefined;
function detectScroll(e:MouseEvent){
    if(!tableBody.value){
        return;
    }
    const x = e.clientX - 15;
    const y = e.clientY - 45;
    if(x<scrollThrs){
        runningHoriDir = "left";
    }else if(x>tableBody.value.clientWidth-scrollThrs){
        runningHoriDir = "right";
    }else{
        runningHoriDir = undefined;
    }
    if(y<scrollThrs){
        runningVertDir = "up";
    }else if(y>tableBody.value.clientHeight-scrollThrs){
        runningVertDir = "down";
    }else{
        runningVertDir = undefined;
    }
}
function stopScroll(){
    runningHoriDir = undefined;
    runningVertDir = undefined;
}
function stopScrollCycle(){
    window.clearInterval(timer);
}
function runScrollCycle(){
    timer = window.setInterval(()=>{
        if(mouseDown.value){
            //在触控板上点击时，mousedown和mouseup之间间隔了几百毫秒，可能造成误触发卷动
            //此处需要判断鼠标已经按下了一段时间才会发生卷动
            if(mouseDownTime>Date.now()-500){return;}
            if(runningHoriDir=="right"){
                tableBody.value?.scrollBy(15,0)
            }else if(runningHoriDir=="left"){
                tableBody.value?.scrollBy(-15,0)
            }
            if(runningVertDir=="up"){
                tableBody.value?.scrollBy(0,-10)
            }else if(runningVertDir=="down"){
                tableBody.value?.scrollBy(0,10)
            }
        }
    },50);
}

const ateLineOp = ref<{dir:Direction, idx:number, mount:HTMLElement}>();
const ateLineSize = {longSide:67, shortSide:67}
function selectSpecificIdx(dir:Direction, idx:number, clickEvent:MouseEvent){
    hideFloatTools();
    hideFloatToolsEntrance();
    selection.value = data.value.selectSpecificIdx(dir,idx);
    const way = thIsExchangeBtn(dir,idx)
    if(way){
        ateLineOp.value = undefined
        if(way=="leftUp")
            idx += 1;
        const success = data.value.exchange({dir:dir, idx:idx})
        if(success)
            saveToHistory()
    }
    else{
        ateLineOp.value = {
            dir,
            idx,
            mount:clickEvent.target as HTMLElement
        }
    }
    refreshMergeBtnsHidding();
}

type LineNumberInfo = {text:string, exchange:boolean, idx:number}
const rowNumbers = computed<LineNumberInfo[]>(()=>{
    const rowCount = data.value.rowCount;
    const arr = new Array<LineNumberInfo>(rowCount);
    const line = ateLineOp.value
    const sel = (line && line.dir == 'horizontal') ? line.idx : undefined
    for(let i=0;i<arr.length;i++){
        if(i+1===sel || i-1===sel){
            arr[i] = {text:'⇅', exchange:true, idx:i}
        }else{
            arr[i] = {text:`${i+1}`, exchange:false, idx:i}
        }
    }
    return arr
})
const colNumbers = computed<LineNumberInfo[]>(()=>{
    const colCount = data.value.colCount;
    const arr = new Array<LineNumberInfo>(colCount);
    const line = ateLineOp.value
    const sel = (line && line.dir == 'vertical') ? line.idx : undefined
    for(let i=0;i<arr.length;i++){
        if(i+1===sel || i-1===sel){
            arr[i] = {text:'⇄', exchange:true, idx:i}
        }else{
            arr[i] = {text:`${i+1}`, exchange:false, idx:i}
        }
    }
    return arr
})
function thIsExchangeBtn(type:Direction, idx:number):"leftUp"|"rightDown"|false{
    if(!ateLineOp.value){
        return false;
    }
    const sidx = ateLineOp.value.idx;
    const sd = ateLineOp.value.dir;
    if(type == sd || type == sd){
        if(idx === sidx-1){
            return "leftUp"
        }
        if(idx === sidx+1){
            return "rightDown";
        }
    }
    return false;
}
const ateLineOpStyle = computed<CSSProperties>(()=>{
    if(!ateLineOp.value){
        return {display:'none'}
    }else{
        const {dir, mount} = ateLineOp.value;
        if(dir==='vertical'){
            const left = mount.offsetLeft + mount.offsetWidth/2 - ateLineSize.longSide/2 - tbodyScrollLeft.value
            return{
                top: mount.clientHeight + 5 +'px',
                left: left+'px',
                width: ateLineSize.longSide+'px',
                height: ateLineSize.shortSide+'px'
            }
        }
        else{
            const tr = mount.parentElement as HTMLElement;
            const top = tr.offsetTop + tr.offsetHeight/2 - ateLineSize.longSide/2 - tbodyScrollTop.value
            return{
                top: top+'px',
                left: mount.clientWidth + 5 +'px',
                width: ateLineSize.shortSide+'px',
                height: ateLineSize.longSide+'px'
            }
        }
    }
})

const auTableEditor = ref<HTMLDivElement>();
const tableBody = ref<HTMLDivElement>();
const selectMode = ref<boolean>(false);
const hideMergeBtn = ref<boolean>(false);
const hideDelMergeBtn = ref<boolean>(false);
const mergeBtn = computed<FloatToolsMergeBtnStatus>(()=>{
    if(!hideDelMergeBtn.value){return 'del'}
    if(!hideMergeBtn.value){return 'merge'}
    return 'none'
})
function refreshMergeBtnsHidding(){
    hideMergeBtn.value = !data.value.isRectSelection(selection.value) || selection.value.length<2;
    hideDelMergeBtn.value = !data.value.isInsideSingleMergeSelection(selection.value);
}
watch(selectMode,(_newVal)=>{
    selection.value=[];hideFloatTools();
})
const mouseDown = ref<boolean>(false);
const ateFloatToolsEntranceSide = 22;
let lastshowFloatToolsEntrance:number = 0;
function showFloatToolsEntrance(e:MouseEvent|HTMLElement){
    if(colorViewMode.value || editMode.value){
        return;
    }
    const time = Date.now()
    if(time-lastshowFloatToolsEntrance<100){
        return;
    }
    lastshowFloatToolsEntrance = time;
    setTimeout(()=>{
        if(selection.value.length>1 && !selectMode.value && !editMode.value){
            showFloatTools();
        }
    }, 10);
    var t:HTMLElement;
    if('target' in e){
        t = e.target as HTMLElement;
        const tag = t.tagName;
        if(!tag || tag.toLowerCase()!="td"){
            return;
        }
    }else{
        t = e;
    }
    ateFloatToolsEntranceShow.value = true
    setFloatToolsPosWith(t);
}
const ateFloatToolsOffsetPos = ref<{left:number, top:number}>({left:0,top:0})
const ateFloatToolsEntranceShow = ref<boolean>(false)
const ateFloatToolsEntranceStyle = computed<CSSProperties>(()=>{
    if(!ateFloatToolsEntranceShow.value){
        return { display:'none' }
    }
    const left = ateFloatToolsOffsetPos.value.left - tbodyScrollLeft.value;
    const top = ateFloatToolsOffsetPos.value.top - tbodyScrollTop.value;
    return {
        top: top+'px',
        left: left+'px',
        display: 'block'
    };
});
const ateFloatToolsShow = ref<boolean>(false)
const ateFloatToolsStyle = computed<CSSProperties>(()=>{
    if(!ateFloatToolsShow.value){
        return { display:'none' }
    }
    const left = ateFloatToolsOffsetPos.value.left - tbodyScrollLeft.value;
    const top = ateFloatToolsOffsetPos.value.top - tbodyScrollTop.value;
    return {
        top: top+'px',
        left: left+'px',
        display: 'block'
    };
});
function setFloatToolsPosWith(t:HTMLElement){
    var top = t.offsetTop + t.clientHeight;
    var left = t.offsetLeft + t.clientWidth;
    top-=ateFloatToolsEntranceSide/2
    left-=ateFloatToolsEntranceSide/2
    ateFloatToolsOffsetPos.value = {left, top}
}
function hideFloatToolsEntrance(){
    ateFloatToolsEntranceShow.value=false
};
function clickToolsEntrance(){
    showFloatTools();
}
function showFloatTools(){
    ateFloatToolsShow.value = true
}
function hideFloatTools(hideEntrance:boolean = true){
    if(hideEntrance){
        hideFloatToolsEntrance();
    }
    ateFloatToolsShow.value = false
}

const textColor = ref<string>(cellTextColorDefault);
const bgColor = ref<string>(cellBgColorDefault);
const colorViewMode = ref<boolean>(false);
function readColor(){
    if(selection.value.length==1 && colorViewMode.value){
        const spos = selection.value[0];
        const colorInfo = data.value.colors.find(x=>x.pos[0]==spos.row && x.pos[1]==spos.col)
        bgColor.value = colorInfo?.bg || cellBgColorDefault;
        textColor.value = colorInfo?.text || cellTextColorDefault;
    }
}
function applyColor(){
    if(selection.value.length>=1 && colorViewMode.value){
        let changed = false;
        selection.value.forEach(spos=>{
            let colorInfo = data.value.colors.find(x=>x.pos[0]==spos.row && x.pos[1]==spos.col)
            if(!colorInfo){
                colorInfo = {pos:[spos.row, spos.col]}
                data.value.colors.push(colorInfo);
            }
            const changedHere = colorInfo.bg !== bgColor.value || colorInfo.text !== textColor.value;
            changed = changed || changedHere;
            colorInfo.bg = bgColor.value;
            colorInfo.text = textColor.value;  
        })
        data.value.rmColorRedundant()
        if(changed){
            saveToHistory()
            data.value.refreshCellShown()
        }
    }
}
function toggleViewMode(){
    colorViewMode.value = !colorViewMode.value;
    if(colorViewMode.value){
        editMode.value = false;
    }
}
function toggleEditMode(){
    editMode.value = !editMode.value;
    if(editMode.value){
        colorViewMode.value = false;
    }
}

function sizeSupportTextDisplay(cellContent:string|null|undefined){
    if(!cellContent){
        return "　"
    }
    if(cellContent.endsWith('\n')){
        return cellContent+"."
    }
    return cellContent
}

const tbodyScrollLeft = ref<number>(0)
const tbodyScrollTop = ref<number>(0)
function tbodyScrollHandler(){
    const tbody = tableBody.value
    if(tbody){
        tbodyScrollLeft.value = tbody.scrollLeft
        tbodyScrollTop.value = tbody.scrollTop
    }
}

const stickyOptionPanelShow = ref(false)
let stickyDataBeforeOpenPanel = ""
function openStickyOptionPanel(){
    stickyDataBeforeOpenPanel = JSON.stringify(data.value)
    stickyOptionPanelShow.value = true
}
function closeStickyOptionPanel(){
    if(stickyDataBeforeOpenPanel != JSON.stringify(data.value)){
        saveToHistory()
    }
    stickyOptionPanelShow.value = false
}
function toggleStickyOptionPanel(){
    if(!stickyOptionPanelShow.value){
        openStickyOptionPanel()
    }else{
        closeStickyOptionPanel()
    }
}

let saveShortcut:ShortcutListener|undefined;
let undoShortcut:ShortcutListener|undefined;
let redoShortcut:ShortcutListener|undefined;
onMounted(()=>{
    runScrollCycle();
    if(!props.noShortcut){
        saveShortcut = new ShortcutListener(save, "s", true);
        saveShortcut.startListen()
        undoShortcut = new ShortcutListener(undo, "z", true);
        undoShortcut.startListen()
        redoShortcut = new ShortcutListener(redo, "z", true, true);
        redoShortcut.startListen()
    }
})
onUnmounted(()=>{
    stopScrollCycle();
    saveShortcut?.dispose()
    undoShortcut?.dispose()
    redoShortcut?.dispose()
})
</script>

<template>
    <div class="auTableEditor" ref="auTableEditor">
        <div v-if="loadComplete" class="ateControl" @click="selection=[];hideFloatTools();" ref="controlDiv">
            <div>
                <div class="ateUndoRedo">
                    <img @click="undo" :src="undoImg" :class="{ready:urAvai.u}"/>
                    <img @click="redo" :src="redoImg" :class="{ready:urAvai.r}"/>
                </div>
                <div class="ateSelectMode">
                    <img @click="selectMode=!selectMode" :src="mutiSelectImg" class="ateBtnIcon ateBtnIconSelectable" :class="{ateSelectedIcon:selectMode}"/>
                </div>
                <div class="ateSelectMode" @click="toggleEditMode">
                    <img :src="editStructureImg" class="ateBtnIcon ateBtnIconSelectable" :class="{ateSelectedIcon:!editMode}"/>
                    <img :src="editContentImg" class="ateBtnIcon ateBtnIconSelectable" :class="{ateSelectedIcon:editMode}"/>
                </div>
                <div class="ateSelectMode">
                    <img @click="toggleViewMode" :src="colorViewImg" class="ateBtnIcon ateBtnIconSelectable" :class="{ateSelectedIcon:colorViewMode}"/>
                    <input type="color" :class="{ateColorInputDisabled:!colorViewMode}" v-model="bgColor"/>
                    <input type="color" :class="{ateColorInputDisabled:!colorViewMode}" v-model="textColor"/>
                    <img :src="colorReadImg" class="ateBtnIcon ateBtnIconClickable" :class="{ateBtnDisabled:!colorViewMode}" @click="readColor"/>
                    <img :src="colorApplyImg" class="ateBtnIcon ateBtnIconClickable" :class="{ateBtnDisabled:!colorViewMode}" @click="applyColor"/>
                </div>
                <div class="ateSelectMode">
                    <img :src="stickyOptionImg" class="ateBtnIcon ateBtnIconSelectable"
                        @click="toggleStickyOptionPanel" :class="{ateSelectedIcon:stickyOptionPanelShow}"/>
                    <div v-if="stickyOptionPanelShow" class="ateStickyOptionPanel">
                        <div>
                            <span>固定第</span>
                            <input type="number" v-model="data.sticky.topIdx" min="0" max="10000"/>
                            <span>行(从第</span>
                            <input type="number" v-model="data.sticky.topFromColIdx" min="0" max="10000"/>
                            <span>列开始)</span>
                        </div>
                        <div>
                            <span>固定第</span>
                            <input type="number" v-model="data.sticky.leftIdx" min="0" max="10000"/>
                            <span>列(从第</span>
                            <input type="number" v-model="data.sticky.leftFromRowIdx" min="0" max="10000"/>
                            <span>行开始)</span>
                        </div>
                        <button @click="closeStickyOptionPanel">OK</button>
                    </div>
                </div>
            </div>
            <div>
                <img :src="saveImg" class="ateBtnIcon" @click="save"/>
            </div>
        </div>
        <div v-if="loadComplete" class="ateTableContainer">
            <table v-if="data" :class="{colorViewMode}">
                <tbody ref="tableBody" @mousemove="detectScroll" @mouseup="stopScroll();mouseCell_end()" @scroll="tbodyScrollHandler">
                    <tr class="topIndex">
                        <th class="ateStickLeft ateStickTop" style="z-index: 11;">
                        </th>
                        <th v-for="c in colNumbers" @click="e=>selectSpecificIdx('vertical', c.idx, e)" class="ateStickTop"
                            :class="{canExchange:c.exchange}">
                            {{ c.text }}
                        </th>
                        <th class="ateStickTop" @click="insertLineOnEdge('right')" style="font-size: 16px;">+</th>
                        <th class="atePlaceholdCell" style="min-width: 100px;">　</th>
                    </tr>
                    <tr v-for="r,ridx in data.cellsShown">
                        <th class="ateStickLeft" @click="e=>selectSpecificIdx('horizontal', ridx, e)"
                            :class="{canExchange:rowNumbers[ridx].exchange}">
                            {{ rowNumbers[ridx].text }}
                        </th>
                        <td v-for="c,cidx in r" :colspan="c.colspan" :rowspan="c.rowspan"
                            @mousedown="mouseCell($event, c.position,'down')"
                            @mouseup="mouseCell($event, c.position, 'up')"
                            @mouseenter="mouseCell($event, c.position,'drag')"
                            @click="mouseCell($event, c.position,'click')"
                            :class="{
                                ateSelected:selectionIdx(c.position)!=-1,
                                isDragging,
                                ateStickyTopRow:(data.sticky.topIdx===c.position.row+1
                                    && (data.sticky.topFromColIdx||0)<=c.position.col+1),
                                ateStickyLeftCol:(data.sticky.leftIdx===c.position.col+1
                                    && (data.sticky.leftFromRowIdx||0)<=c.position.row+1)
                                }"
                            :style="colorViewMode?{backgroundColor:c.bgColor||cellBgColorDefault, color:c.textColor||cellTextColorDefault}:{}">
                            {{ sizeSupportTextDisplay(c.content) }}<textarea 
                                v-show="editMode" v-model="c.content" @blur="textareaBlurHandler($event,ridx,cidx)"
                                class="ateCellEdit" spellcheck="false"></textarea>
                            <!--textarea必须紧贴左边的{{}}不能换行-->
                        </td>
                        <td class="atePlaceholdCell">　</td>
                    </tr>
                    <tr>
                        <th class="ateStickLeft" @click="insertLineOnEdge('bottom')"
                            style="height: 30px;font-size: 16px;">+</th>
                        <td class="atePlaceholdCell">　</td>
                    </tr>
                    <tr>
                        <th class="atePlaceholdCell" style="height: 55px;">　</th>
                        <td class="atePlaceholdCell">　</td>
                    </tr>
                </tbody>
            </table>
            <div class="ateFloatToolsEntrance" :style="ateFloatToolsEntranceStyle" @mousedown="clickToolsEntrance">
                <img :src="settingsImg"/>
            </div>
            <div class="ateFloatTools" :style="ateFloatToolsStyle">
                <FloatTools :merge-btn="mergeBtn" @click-btn="ateFloatToolsEmiteHandler"></FloatTools>
            </div>
            <div class="ateLineOp" :style="ateLineOpStyle">
                <FloatToolsForLine :dir="ateLineOp?.dir" @click-btn="ateFloatToolsEmiteHandler"></FloatToolsForLine>
            </div>
        </div>
        <div v-else class="ateLoading">
            表格编辑器加载中 | 请稍后
        </div>
        <div class="ateAuthor" v-if="!noAds">
            <span>au-table-editor</span>
            <a href="https://www.npmjs.com/package/@aurouscia/au-table-editor" target="_blank">npm</a>
            <a href="https://gitee.com/au114514/au-table-editor" target="_blank">git</a>
        </div>
        <div class="ateInfo" :class="{err:isErrInfo}" :style="infoBoxTopStyle">
            {{ info }}
        </div>
    </div>
</template>

<style scoped lang="scss">
.ateLineOp{
    position: absolute;
    display: block;
    border: 2px solid black;
    border-radius: 0px;
    background-color: #ccc;
    z-index: 11;
}
img.ateBtnIcon{
    width: 24px;
    height: 24px;
    object-fit: contain;
    cursor: pointer;
}
img.ateBtnDisabled{
    border: 2px solid white !important;
    cursor: not-allowed !important;
}
.ateColorInputDisabled{
    cursor: not-allowed !important;
}
img.ateBtnIconClickable{
    border: 2px solid #ccc;
}
img.ateBtnIconClickable:hover{
    border: 2px solid #666;
}
img.ateBtnIconSelectable{
    border: 2px solid #ccc;
}
img.ateSelectedIcon{
    border: 2px solid green;
}
.ateAuthor a:hover{
    color:#666;
}
.ateAuthor a{
    color:#ddd;
}
.ateAuthor{
    color:#ddd;
    font-size: 12px;
    line-height: 18px;
    display: flex;
    padding-right: 8px;
    gap:8px;
    justify-content: right;
    align-items: center;
}
.ateFloatToolsEntrance:hover{
    background-color: rgb(97, 97, 212);
}
.ateFloatToolsEntrance{
    position: absolute;
    display: none;
    width: 22px;
    height: 22px;
    border: 2px solid #fff;
    background-color: rgb(135, 135, 228);
    border-radius: 2px;
    z-index: 13;
}
.ateFloatToolsEntrance img{
    object-fit: contain;
    position: relative;
    width: 100%;height: 100%;
}
.ateFloatTools{
    position: absolute;
    display: none;
    border: 2px solid black;
    border-radius: 0px;
    z-index: 14;
}
textarea.ateCellEdit{
    resize: none;
    overflow:hidden;
    position: absolute;
    top:0px;left:0px;right:0px;bottom: 0px;
    padding: 4px 0px 4px 0px;
    font-size:16px;
    font-family: sans-serif;
    white-space: pre;
    word-break: break-all;
    text-align: center;
    border: 1.5px solid #999;
    border-radius: 0px;
}
.ateLoading{
    position: absolute;
    left:0px;right:0px;
    top: 20px;
    font-size: 25px;
    text-align: center;
    color:#999
}
.ateInfo.err{
    background-color: plum;
}
.ateInfo{
    position: absolute;
    top: -30px;
    left:0px;right:0px;
    width: fit-content;
    padding: 10px;
    margin: auto;
    background-color: olivedrab;
    border-radius: 10px;
    border:2px solid white;
    color:white;
    transition: 0.5s;
    z-index: 12;
}
.ateUndoRedo img{
    width: 1.3em;
    height: 1.3em;
    object-fit: contain;
    background-color: #999;
    border-radius: 3px;
    border: 2px solid white;
    transition: 0.5s;
}
.ateUndoRedo img.ready{
    background-color: olivedrab;
    border-color: white;
}
.ateUndoRedo img.ready:hover{
    border: 2px solid black;
}
.ateUndoRedo{
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    gap: 5px
}
.ateStickyOptionPanel{
    position: absolute;
    width: 240px;
    height: 100px;
    right: 20px;
    top: 20px;
    background-color: white;
    border-radius: 10px;
    box-shadow: 0px 0px 5px 0px black;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 3px;
    color: #999;
    font-size: 14px;
    button{
        padding: 3px;
        background-color: olivedrab;
        color: white;
        border: 2px solid white;
        border-radius: 5px;
        font-size: 16px;
        cursor: pointer;
        &:hover{
            background-color: green;
        }
    }
    &>div{
        display: flex;
        gap: 2px;
        input{
            text-align: center;
            width: 40px;
            border: none;
            background-color: #ccc;
            font-size: 18px;
        }
    }
}
.ateSelectMode{
    display: flex;
    align-items: center;
    background-color: white;
    padding: 2px;
    gap:3px
}
.ateSelectMode input[type=color]{
    width: 18px;
    height: 28px;
    border: none;
    cursor: pointer;
}
.ateControl>div{
    display: flex;
    column-gap:8px;
    row-gap: 6px;
    align-items: center;
}
.ateControl>div:first-child{
    flex-shrink: 1;
    overflow-x:auto ;
    flex-wrap: wrap;
}
.ateControl>div:first-child *{
    flex-shrink: 0;
}
.ateControl>div:nth-child(2){
    flex-shrink: 0;
}
.ateControl{
    background-color: #aaa;
    border-radius: 5px;
    padding: 5px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: nowrap;
    z-index: 20;
    flex-shrink: 0;
}
.ateTableContainer{
    width: 100%;
    position: relative;
    flex-grow: 1;
}
table{
    border-collapse: separate;
    background-color: white;
    border-spacing: 2px;
}
.atePlaceholdCell{
    background-color: transparent !important;
    border: none !important;
    padding: 3px;
}
td{
    background-color: gray;
    padding: 5px;
    color:white;
    min-width: 60px;
    text-align: center;
    border-radius: 0px;
    white-space: pre;
    word-break: break-all;
    font-size: 16px;
    font-family: sans-serif;
    position: relative;
}
table.colorViewMode td{
    padding: 3px;
    border: 2px solid white
}
table.colorViewMode td.ateSelected{
    border-color: black;
}
tbody{
    position: absolute;
    left: 0px;right: 0px;top: 0px;bottom:0px;
    overflow: auto;
}
.ateStickLeft{
    position: sticky;
    left: 3px;
    background-color: white;
    box-shadow: 1px 0px 3px black;
    z-index: 10;
    font-size: 12px;
    width: 22px;
}
.ateStickyLeftCol{
    position: sticky;
    left: 25px; /* ateStickLeft的left的"3"+其宽度的"22"*/
    z-index: 7;
    box-shadow: 0px 0px 3px white;
}
.ateStickTop{
    position: sticky;
    top: 3px;
    background-color: white;
    box-shadow: 0px 1px 3px black;
    z-index: 10;
}
.ateStickyTopRow{
    position: sticky;
    top: 25px; /* ateStickTop的top的"3"+topIndex的高度"22"*/
    z-index: 8;
    box-shadow: 0px 0px 3px white;
}
.ateStickyTopRow.ateStickyLeftCol{
    z-index: 9;
}
.topIndex{
    height: 22px;
    font-size: 12px;
}
th.canExchange{
    background-color: #90c539;
    color: white;
}
th.canExchange:hover{
    background-color: green;
}
th{
    color:gray;
    font-weight: normal;
    border: 1px solid gray;
    transition: 0.2s;
}
th:hover{
    background-color: #ccc;
}
td.ateSelected.isDragging{
    background-color: rgb(64, 131, 23);
}
td.ateSelected{
    background-color: olivedrab;
}
.auTableEditor{
    min-width: 300px;
    position: absolute;
    left: 0px;right: 0px;top: 0px;bottom: 0px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
}

*{
  margin: 0px;
  padding: 0px;
  white-space: nowrap;
  user-select: none;
  overflow: hidden;
  border-radius: 3px;
}
</style>
